{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.6.9"
    },
    "colab": {
      "name": "main.ipynb",
      "provenance": [],
      "collapsed_sections": [
        "wEciiaKxv05e",
        "bWUHnQzPv05x",
        "R2yCSSvgv06O",
        "82EUrOmIv07V",
        "ueX6QlScv07W"
      ]
    }
  },
  "cells": [
    {
      "cell_type": "code",
      "metadata": {
        "id": "1hNDXv_QzWu7",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        ""
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mWp5U3XBv04F",
        "colab_type": "text"
      },
      "source": [
        "# Exploring Bayesian Optimization\n",
        "\n",
        "In this post, we are going to focus on two tasks, active learning - where we query the user/oracle to label samples; and the multi-arm bandit - where we again query the user/Oracle which returns us a scalar reward. We will be trying to pose the problems first and then talk about some of the ways to solve these problems.\n",
        "\n",
        "The primary motivation behind active learning is the expensive cost of labeling in machine learning tasks.\n",
        "\n",
        "## Mining Gold!\n",
        "\n",
        "Let us explain the two problems using the gold mining application.\n",
        "We will, for now, look at only one-dimensional locations, i.e., we are talking gold distribution only about a line.\n",
        "The issue we have is that at the start of the activity, we have no idea about the amount of gold at different locations.\n",
        "The only way we can get the information about the amount of gold is by drilling at a location.\n",
        "This drilling is costly and involves expensive sensors to be used.\n",
        "We, therefore, want to minimize the number of drillings that we require.\n",
        "\n",
        "We below show two of the common objectives for the gold mining problem.\n",
        "\n",
        "- **Problem 1: Best Estimate of Gold Distribution**\n",
        "  In this problem, we are supposed to estimate the amount of gold on the one-dimensional line. But we can not drill at every location. We should drill at those locations that provide us the \"maximum\" information about the distribution of the gold.\n",
        "  \n",
        "- **Problem 2: Location of Maximum Gold**\n",
        "  In this problem, we are supposed to find the location in the one-dimensional space where the gold quantity is the maximum. This problem focuses on finding the location with the most gold content.\n",
        "\n",
        "\n",
        "![](MAB_gifs/active-gp.gif)\n",
        "\n",
        "---\n",
        "\n",
        "![](MAB_gifs/mab-gp-pi-eps0.5.gif)\n",
        "\n",
        "- Here are representative animations showing the process of drilling at new locations and to reduce the uncertainty and get the best predictions showcasing the Active Learning problem.\n",
        "\n",
        "- And drilling at locations to get the location of the maximum gold reserve, showcasing the Multi Armed Bandit problem\n",
        "\n",
        "We will build the solution to both of these problems from the ground up.\n",
        "\n",
        "#### Some imports"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "O5S4bQgQv04J",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 188
        },
        "outputId": "98eadda3-25d7-4dac-b952-e761217e5f57"
      },
      "source": [
        "!pip install GPy\n",
        "!sudo apt-get install msttcorefonts -qq"
      ],
      "execution_count": 1,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Requirement already satisfied: GPy in /usr/local/lib/python3.6/dist-packages (1.9.9)\n",
            "Requirement already satisfied: numpy>=1.7 in /usr/local/lib/python3.6/dist-packages (from GPy) (1.18.3)\n",
            "Requirement already satisfied: six in /usr/local/lib/python3.6/dist-packages (from GPy) (1.12.0)\n",
            "Requirement already satisfied: scipy>=0.16 in /usr/local/lib/python3.6/dist-packages (from GPy) (1.4.1)\n",
            "Requirement already satisfied: paramz>=0.9.0 in /usr/local/lib/python3.6/dist-packages (from GPy) (0.9.5)\n",
            "Requirement already satisfied: decorator>=4.0.10 in /usr/local/lib/python3.6/dist-packages (from paramz>=0.9.0->GPy) (4.4.2)\n",
            "E: Failed to fetch http://archive.ubuntu.com/ubuntu/pool/main/u/update-manager/python3-update-manager_18.04.11.10_all.deb  404  Not Found [IP: 91.189.88.142 80]\n",
            "E: Failed to fetch http://archive.ubuntu.com/ubuntu/pool/main/u/update-manager/update-manager-core_18.04.11.10_all.deb  404  Not Found [IP: 91.189.88.142 80]\n",
            "E: Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "2cCqKyCSv04e",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "import os\n",
        "import json\n",
        "import warnings\n",
        "import itertools\n",
        "\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "# from sklearn.gaussian_process.kernels import Matern\n",
        "from GPy.mappings.constant import Constant\n",
        "from GPy.kern import Matern52\n",
        "from GPy.models import GPRegression\n",
        "\n",
        "from scipy.special import ndtr\n",
        "from scipy.stats import norm\n",
        "\n",
        "warnings.filterwarnings('ignore')\n",
        "# plt.style.use('classic')\n",
        "plt.style.use('seaborn-paper')\n",
        "%matplotlib inline"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "scrolled": true,
        "id": "voCOA5E6v04m",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# plotting styles\n",
        "\n",
        "def nnsvm(rcParams, i=0):\n",
        "    rcParams['font.family'] = 'serif'\n",
        "    rcParams['font.serif'] = 'Ubuntu'\n",
        "    rcParams['font.monospace'] = 'Ubuntu Mono'\n",
        "    rcParams['font.size'] = 12 + i\n",
        "    rcParams['axes.labelsize'] = 12 + i\n",
        "    rcParams['axes.labelweight'] = 'normal'\n",
        "    rcParams['xtick.labelsize'] = 10 + i\n",
        "    rcParams['ytick.labelsize'] = 10 + i\n",
        "    rcParams['legend.fontsize'] = 12 + i\n",
        "    rcParams['figure.titlesize'] = 14 + i\n",
        "    rcParams['lines.linewidth']= 2.7\n",
        "    rcParams['axes.titlesize'] = 14 + i\n",
        "alpha_plt = 0.3 # beautification\n",
        "    \n",
        "# gifs stuff\n",
        "gifDir = 'MAB_gifs'\n",
        "os.makedirs(gifDir, exist_ok=True)\n",
        "delay = 80"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8q_4D4aUv04v",
        "colab_type": "text"
      },
      "source": [
        "#### Prettyfing Matplotlib plots"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "jCXuUfRRv04x",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 270
        },
        "outputId": "992f22f2-8c5d-4de2-94cf-96434a3d0faf"
      },
      "source": [
        "plt.gca().grid()"
      ],
      "execution_count": 4,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 432x288 with 1 Axes>"
            ]
          },
          "metadata": {
            "tags": [],
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "AI4BtoDKv047",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "SPINE_COLOR = 'gray'\n",
        "\n",
        "def format_axes(ax):\n",
        "    \n",
        "    for spine in ['top', 'right']:\n",
        "        ax.spines[spine].set_visible(False)\n",
        "\n",
        "    for spine in ['left', 'bottom']:\n",
        "        ax.spines[spine].set_color(SPINE_COLOR)\n",
        "        ax.spines[spine].set_linewidth(0.5)\n",
        "\n",
        "    ax.xaxis.set_ticks_position('bottom')\n",
        "    ax.yaxis.set_ticks_position('left')\n",
        "\n",
        "    for axis in [ax.xaxis, ax.yaxis]:\n",
        "        axis.set_tick_params(direction='out', color=SPINE_COLOR)\n",
        "        \n",
        "    ax.grid(alpha=.25)\n",
        "\n",
        "    return ax"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FsSgad-Yv05A",
        "colab_type": "text"
      },
      "source": [
        "### Active Learning\n",
        "\n",
        "**Problem 1** is very similar to problems we like to solve using active learning. Active learning is used to predict distribution by reducing uncertainty. One of the ways we can reduce the uncertainty is by choosing the point at which we have the maximum variance (we are most uncertain).\n",
        "\n",
        "### Gaussian Processes\n",
        "\n",
        "In our previous post, [here](GP-1.html); we had introduced the Gaussian Process. As you can remember, we use Gaussian Processes to get a prediction as well as the attached uncertainty (variance) with that prediction. This will turn out to be useful for us, as we wanted to drill where we were most uncertain. By using Gaussian processes, we take some very naive assumption that the gold distribution of nearby points in similar (smoothness)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ei3lUeLbv05C",
        "colab_type": "text"
      },
      "source": [
        "#### Creating ground truth data"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "KaZawyDHv05D",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def f(x):\n",
        "    \"\"\"The function to predict.\"\"\"\n",
        "    return 2*((x-3)*np.sin((x-3))) + 2 + 0.5*x"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HNd4tq4Cv05J",
        "colab_type": "text"
      },
      "source": [
        "Let us now try to see how our groundtruth data looks like."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "FTJMWeClv05K",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "nnsvm(plt.rcParams, -2)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "GuzqNpmjv05P",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 346
        },
        "outputId": "d261cfff-3b7f-4e36-f593-b0170a009710"
      },
      "source": [
        "x = np.atleast_2d(np.linspace(0, 6, 600)).T\n",
        "plt.plot(x, f(x), color='purple', label='GT')\n",
        "plt.xlabel(\"X\")\n",
        "plt.ylabel(\"Gold content\")\n",
        "plt.title(\"Ground Truth for Gold Content\")\n",
        "format_axes(plt.gca())\n",
        "plt.savefig('MAB_gifs/GT.svg', bbox_inches=\"tight\")\n",
        "plt.show()"
      ],
      "execution_count": 8,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "findfont: Font family ['serif'] not found. Falling back to DejaVu Sans.\n",
            "findfont: Font family ['serif'] not found. Falling back to DejaVu Sans.\n",
            "findfont: Font family ['serif'] not found. Falling back to DejaVu Sans.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 432x288 with 1 Axes>"
            ]
          },
          "metadata": {
            "tags": [],
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "U3SKDourv05V",
        "colab_type": "text"
      },
      "source": [
        "#### Prior Model\n",
        "\n",
        "Our prior model doesn't know much and assumes a smooth relationship between points via an Matern kernel. The Grey line in the graph below denotes the knowledge we have about the gold content without drilling even at a single location."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "scrolled": false,
        "id": "4yFeQxWUv05W",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 313
        },
        "outputId": "cafefa42-7756-4fd7-b559-fbcb189281b4"
      },
      "source": [
        "# kernel = Matern(length_scale=1.0)\n",
        "# kernel = None\n",
        "\n",
        "y_pred, var = 5*np.ones(x.shape).flatten(), 1.1*np.ones(x.shape).flatten()\n",
        "sigma = np.sqrt(var).squeeze()\n",
        "\n",
        "plt.plot(x, y_pred, 'k', label='Predicted ($\\mu$)')\n",
        "plt.plot(x, f(x), 'purple', label=r'Ground Truth ($f$)')\n",
        "plt.xlabel(\"X\")\n",
        "plt.ylabel(\"Gold content\")\n",
        "plt.title(\"Prior\")\n",
        "plt.fill_between(x.flatten(), y_pred+sigma, y_pred-sigma,\n",
        "                 color='gray', \n",
        "                 alpha=alpha_plt, label=r'$\\mu \\pm \\sigma$')\n",
        "plt.legend(bbox_to_anchor=(1.04,1), loc=\"upper left\")\n",
        "format_axes(plt.gca())\n",
        "plt.savefig('MAB_gifs/prior.svg', bbox_inches=\"tight\")"
      ],
      "execution_count": 9,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "findfont: Font family ['serif'] not found. Falling back to DejaVu Sans.\n"
          ],
          "name": "stderr"
        },
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 432x288 with 1 Axes>"
            ]
          },
          "metadata": {
            "tags": [],
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wEciiaKxv05e",
        "colab_type": "text"
      },
      "source": [
        "Also, take notice that the confidence (uncertainty) about the gold content is also the same for every location.\n",
        "\n",
        "####  Adding Training Data\n",
        "Let us now add a point to the train set or in other words, drill one of the locations and see the gold content (`y`). We can see how our confidence and our estimates change after we get this first information by fitting the model to the new data. I am going to add `(x = 0.5, y = f(0.5))` into the train set now."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "FZG28l8jv05g",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "train_X = np.atleast_2d([0.5, 0.12]).T\n",
        "train_y = f(train_X)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "9F9Pc9fwv05n",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def gp_creator(x, y, val = 5):\n",
        "    y.resize(x.shape[0], 1)\n",
        "    ker = Matern52(x.shape[1], ARD=True, variance=1)\n",
        "#     print (x, x.mean())\n",
        "    mean_fn = Constant(x.shape[1], 1, value=val)\n",
        "    gp = GPRegression(x, y, kernel=ker, noise_var=0,\n",
        "                      mean_function = mean_fn)\n",
        "    gp.optimize()\n",
        "    return gp"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "HJYmQQ_Hv05s",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0
        },
        "outputId": "c70e4748-a65c-4272-c930-3f790cb9405c"
      },
      "source": [
        "train_X = np.atleast_2d([0.5]).T\n",
        "train_y = f(train_X).flatten()\n",
        "gp = gp_creator(train_X, train_y)\n",
        "# gp.fit(train_X, train_y)\n",
        "# gp.optimize()\n",
        "y_pred, var = [t.flatten() for t in gp.predict(x)]\n",
        "sigma = np.sqrt(var).squeeze()\n",
        "\n",
        "plt.plot(x, y_pred, 'k', label=r'Predicted ($\\mu$)')\n",
        "plt.plot(x, f(x), 'purple', label=r'Ground Truth ($f$)')\n",
        "plt.xlabel(\"X\")\n",
        "plt.title(\"Posterior\")\n",
        "plt.ylabel(\"Gold content\")\n",
        "plt.fill_between(x.flatten(), y_pred+sigma, y_pred-sigma, color='gray', alpha=alpha_plt, label=r'$\\mu \\pm \\sigma$')\n",
        "plt.scatter(train_X, train_y, color='red', s=300, zorder=10, label='Last Added Point')\n",
        "plt.legend(bbox_to_anchor=(1.04,1), loc=\"upper left\")\n",
        "format_axes(plt.gca())\n",
        "plt.savefig('MAB_gifs/posterior.svg', bbox_inches=\"tight\")"
      ],
      "execution_count": 12,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 432x288 with 1 Axes>"
            ]
          },
          "metadata": {
            "tags": [],
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bWUHnQzPv05x",
        "colab_type": "text"
      },
      "source": [
        "Nice! We see now that the posterior has changed and we are very certain about the gold content in the vicinity of `x = 0.5`, but, very uncertain far away from it. Also, we can see that the mean of the point closer to `x = 0.5` is closer to the value that we got from drilling and seeing the gold content. So, we now come to the key idea.\n",
        "\n",
        "#### Active Learning Procedure\n",
        "\n",
        "1. Choose the point of having the highest uncertainty\n",
        "2. Add the point to train set\n",
        "3. Train on the new train set\n",
        "4. Go to 1 till convergence or budget elapsed"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "31_FxfKjv05y",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0
        },
        "outputId": "3fadec08-585c-4951-9d31-35775a27c8c2"
      },
      "source": [
        "f(x).mean()"
      ],
      "execution_count": 13,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "5.572003681521661"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 13
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "b_szNzRwv053",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0
        },
        "outputId": "f078dee3-5a10-4775-83a1-d544a049103d"
      },
      "source": [
        "# Stopping criteria is 10 iterations\n",
        "nnsvm(plt.rcParams, 2)\n",
        "for i in range(10):\n",
        "    # Fit on current train set\n",
        "    gp = gp_creator(train_X, train_y)\n",
        "#     gp.optimize(?)\n",
        "    # predict on current pool set\n",
        "    y_pred, var = [t.flatten() for t in gp.predict(x)]\n",
        "    sigma = np.sqrt(var).squeeze()\n",
        "    \n",
        "    plt.plot(x, y_pred, 'k', label=r'Predicted ($\\mu$)')\n",
        "    plt.plot(x, f(x), 'purple', label=r'Ground Truth ($f$)')\n",
        "    plt.xlabel(\"X\")\n",
        "    plt.ylabel(\"Gold content\")\n",
        "    plt.fill_between(x.flatten(), y_pred+sigma, y_pred-sigma, \n",
        "                     color='gray', alpha=alpha_plt, label=r'$\\mu \\pm \\sigma$')\n",
        "    plt.scatter(train_X[:-1], train_y[:-1], color='black', s=300, zorder=10, label='Training points')\n",
        "    plt.scatter(train_X[-1], train_y[-1], color='red', s=300, zorder=10, label='Query Point')\n",
        "    plt.legend(bbox_to_anchor=(1.04,1), loc=\"upper left\")\n",
        "    plt.title(f\"Iteration: {i}\")\n",
        "    format_axes(plt.gca())\n",
        "    plt.ylim((1, 9))\n",
        "    plt.xlim((-1, 7))\n",
        "    dirName = 'active-gp-img'\n",
        "    os.makedirs(dirName, exist_ok=True)\n",
        "    plt.savefig(f\"{dirName}/{i}.png\", bbox_inches=\"tight\", dpi=180)\n",
        "    plt.close()\n",
        "    \n",
        "    # Choose the next point with highest sigma\n",
        "    next_ix = np.argmax(sigma)\n",
        "    next_x = x[next_ix]\n",
        "    # Add new point with highest uncertainty to the pool set\n",
        "    train_X = np.vstack([train_X, [x[next_ix]]])\n",
        "    train_y = f(train_X)"
      ],
      "execution_count": 14,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "findfont: Font family ['serif'] not found. Falling back to DejaVu Sans.\n",
            "findfont: Font family ['serif'] not found. Falling back to DejaVu Sans.\n"
          ],
          "name": "stderr"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wcdoNXomv06B",
        "colab_type": "text"
      },
      "source": [
        "Let us now automate this process and see how our posterior changes at every iteration where we add a sensor. For each of our iteration below, the prior was the Gaussian Process learned on the points already in the training set. We have recreated the 1st animation at the top of the post!"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "A1s2Ie9jv06C",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# !convert -delay {delay} -loop 0 active-gp-img/*.png MAB_gifs/active-gp.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Y0EZYEV6v06J",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0
        },
        "outputId": "61e6071b-76e8-4565-9a95-c2b442233a43"
      },
      "source": [
        "gp"
      ],
      "execution_count": 16,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/html": [
              "<style type=\"text/css\">\n",
              ".pd{\n",
              "    font-family: \"Courier New\", Courier, monospace !important;\n",
              "    width: 100%;\n",
              "    padding: 3px;\n",
              "}\n",
              "</style>\n",
              "\n",
              "<p class=pd>\n",
              "<b>Model</b>: GP regression<br>\n",
              "<b>Objective</b>: 22.772512176772885<br>\n",
              "<b>Number of Parameters</b>: 4<br>\n",
              "<b>Number of Optimization Parameters</b>: 4<br>\n",
              "<b>Updates</b>: True<br>\n",
              "</p>\n",
              "<style type=\"text/css\">\n",
              ".tg  {font-family:\"Courier New\", Courier, monospace !important;padding:2px 3px;word-break:normal;border-collapse:collapse;border-spacing:0;border-color:#DCDCDC;margin:0px auto;width:100%;}\n",
              ".tg td{font-family:\"Courier New\", Courier, monospace !important;font-weight:bold;color:#444;background-color:#F7FDFA;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#DCDCDC;}\n",
              ".tg th{font-family:\"Courier New\", Courier, monospace !important;font-weight:normal;color:#fff;background-color:#26ADE4;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:#DCDCDC;}\n",
              ".tg .tg-left{font-family:\"Courier New\", Courier, monospace !important;font-weight:normal;text-align:left;}\n",
              ".tg .tg-center{font-family:\"Courier New\", Courier, monospace !important;font-weight:normal;text-align:center;}\n",
              ".tg .tg-right{font-family:\"Courier New\", Courier, monospace !important;font-weight:normal;text-align:right;}\n",
              "</style>\n",
              "<table class=\"tg\"><tr><th><b>  GP_regression.         </b></th><th><b>                 value</b></th><th><b>constraints</b></th><th><b>priors</b></th></tr>\n",
              "<tr><td class=tg-left>  constmap.C             </td><td class=tg-right>                   5.0</td><td class=tg-center>           </td><td class=tg-center>      </td></tr>\n",
              "<tr><td class=tg-left>  Mat52.variance         </td><td class=tg-right>                   1.0</td><td class=tg-center>    +ve    </td><td class=tg-center>      </td></tr>\n",
              "<tr><td class=tg-left>  Mat52.lengthscale      </td><td class=tg-right>                   1.0</td><td class=tg-center>    +ve    </td><td class=tg-center>      </td></tr>\n",
              "<tr><td class=tg-left>  Gaussian_noise.variance</td><td class=tg-right>5.562684646268137e-309</td><td class=tg-center>    +ve    </td><td class=tg-center>      </td></tr>\n",
              "</table>"
            ],
            "text/plain": [
              "<GPy.models.gp_regression.GPRegression at 0x7f32d380f828>"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 16
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "R2yCSSvgv06O",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/active-gp.gif)\n",
        "\n",
        "There you go we have recreated one of the plots from the starting of the blog! One point to notice is that this idea of choosing the most uncertain location leads to querying of the points that are the farthest (visible when we choose the 2nd location to drill). This might not be so good as we are kind of wasting our drillings because they are at the boundary of the 1-dimensional plot.\n",
        "\n",
        "----\n",
        "\n",
        "### Multi-Armed Bandit\n",
        "\n",
        "**Problem 2** requires us to find the location where the gold content is maximum. Even though the problem setting may be similar, the objective is quite different than problem 1. In other words, we just want the location where we can drill to get the most gold.\n",
        "\n",
        "Older problem - Earlier in the active learning problem, our motivation for drilling at locations was to predict the distribution of the gold content over all the locations in the one-dimensional line. We, therefore, had chosen the next location to drill where we had maximum uncertainty about our estimate.\n",
        "\n",
        "In this problem, we are instead interested to know the location at which we find the maximum gold. For getting the location of maximum gold content, we might want to drill at the location where predicted mean is the highest (exploit). But unfortunately our mean is not always accurate, so we need to correct our mean (reduce variance / explore) too. Multi-Arm Bandit looks at both exploitation and exploration, whereas in the case of Active Learning Problem, we only cared about exploration.\n",
        "\n",
        "#### Acquisition Functions\n",
        "\n",
        "Now, to take into account the combination of exploration and exploitation, we try to use a function which combines the two sides. These utility functions that take into account both exploration and exploitation in multi-arm bandit problem are called acquisition functions."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "EW0AG6K-v06O",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "val = 5"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "tX7GKF1Cv06S",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        ""
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Ha6chP1jv06V",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "class ACQ:\n",
        "    def acq_fn(self, *args, **kwargs):\n",
        "        raise NotImplemented\n",
        "    def __str__(self):\n",
        "        return self.__class__.__name__\n",
        "    def __call__(self, *args, **kwargs):\n",
        "        return self.acq_fn(*args, **kwargs)\n",
        "\n",
        "class ACQ1(ACQ):\n",
        "    def acq_fn(self, gp, x, lam = 0.4, **kwrags):\n",
        "        \"\"\"\n",
        "        gp: sklearn.GPRegresssor\n",
        "        lam: float, where the objective is: \\mu(x) + \\lambda \\sigma(x)\n",
        "        \"\"\"\n",
        "        y_pred, var = [t.flatten() for t in gp.predict(x)]\n",
        "        sigma = np.sqrt(var).squeeze()\n",
        "        \n",
        "        return y_pred + lam*sigma"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "NW4SbShAv06a",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 0
        },
        "outputId": "866a0db8-feb7-4799-c632-721296b3d8b7"
      },
      "source": [
        "acq_obj = ACQ1()\n",
        "\n",
        "train_X = np.atleast_2d([0.5]).T\n",
        "train_y = f(train_X)\n",
        "gp = gp_creator(train_X, train_y)\n",
        "y_pred, var = [t.flatten() for t in gp.predict(x)]\n",
        "sigma = np.sqrt(var).squeeze()\n",
        "\n",
        "plt.plot(x, y_pred, 'k', label=r'Predicted ($\\mu$)')\n",
        "plt.plot(x, f(x), 'purple', label=r'Ground Truth ($f$)')\n",
        "plt.xlabel(\"X\")\n",
        "plt.ylabel(\"Gold content\")\n",
        "plt.fill_between(x.flatten(), y_pred+sigma, y_pred-sigma, \n",
        "                 color='gray', alpha=alpha_plt, label=r'$\\mu \\pm \\sigma$')\n",
        "plt.scatter(train_X, train_y, color='red', s=200, zorder=10, label='Training points')\n",
        "plt.plot(x, acq_obj(gp, x, 5), label='Acquisition function', color='green')\n",
        "format_axes(plt.gca())\n",
        "plt.legend(bbox_to_anchor=(1.04,1), loc=\"upper left\")\n",
        "plt.savefig(f\"MAB_gifs/acq_fn.svg\", bbox_inches=\"tight\")"
      ],
      "execution_count": 19,
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "image/png": "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\n",
            "text/plain": [
              "<Figure size 432x288 with 1 Axes>"
            ]
          },
          "metadata": {
            "tags": [],
            "needs_background": "light"
          }
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RRmmj2TRv06f",
        "colab_type": "text"
      },
      "source": [
        "Here, we can see that mean near the location of the just added point (red point) is high. But as we go far from the red point, we see that our uncertainty increases to a maximum. As we discussed in multi-arm bandit problem, we like to have some combination of exploration and exploitation. The most basic way to do so is by linearly combining the two values.\n",
        "\n",
        "### ACQ1\n",
        "\n",
        "This combined value that takes into account exploration and exploitation is referred to as the acquisition value, returned by acquisition function. We see at around the location `x = 1.4` we get the maximum value for the acquisition (green curve). Thus we next select this location to drill.\n",
        "\n",
        "The intuition of using the acquisition function `mean + lam * uncertainty` is that we are interested in finding the global mean, so taking into account the estimated mean would be a good idea. Additionally, we would like to explore too (using `lam`); else we might be stuck in a local minimum if don't explore too much (see below).\n",
        "\n",
        "We define a big plotting function below for saving us the trouble down the road."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "7l2lntAXv06g",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def rargmax(b):\n",
        "    \"\"\"Randomly tie breaking argmax\"\"\"\n",
        "    return np.random.choice(np.flatnonzero(b == b.max()))\n",
        "\n",
        "def plot_acquisition(\n",
        "        train_X, train_y,\n",
        "        acq_class, val,  acq_params={},\n",
        "        ylim0=None, xlim0=None,\n",
        "        ylim1=None, xlim1=None,\n",
        "        y1scale='linear', it = 10,\n",
        "        seed = 0\n",
        "    ):\n",
        "    \"\"\"acq_params would contain the parameters for our acq_fn\"\"\"\n",
        "    np.random.seed(seed)\n",
        "    # for storing the max till it iters\n",
        "    max_till_now = []\n",
        "    \n",
        "    # Stopping criteria is 10 iterations\n",
        "    for i in range(it):\n",
        "        fig, ax = plt.subplots(nrows=2, sharex=True)\n",
        "        ax[1].set_yscale(y1scale)\n",
        "        \n",
        "        # limits\n",
        "        ax[0].set_ylim(ylim0) if ylim0 is not None else ax[0].set_ylim(min(f(x)-.5), max(f(x))+.5)\n",
        "        ax[0].set_xlim(xlim0) if xlim0 is not None else ax[0].set_xlim(min(x)-.5, max(x)+.5)\n",
        "        ax[1].set_ylim(ylim1) if ylim1 is not None else None\n",
        "        ax[1].set_xlim(xlim1) if xlim1 is not None else ax[1].set_xlim(min(x)-.5, max(x)+.5)\n",
        "        \n",
        "        # fitting\n",
        "        gp = gp_creator(train_X, train_y, val)\n",
        "        \n",
        "        # plot Acquisition\n",
        "        acq_obj = acq_class()\n",
        "        acquisition = acq_obj(gp, x, t=i+1, \n",
        "                              **acq_params, \n",
        "                              mu=max(train_y))\n",
        "        ax[1].plot(x, acquisition, label='Acquisition function', color='green')\n",
        "        \n",
        "        # predict on current pool set\n",
        "        y_pred, var = [t.flatten() for t in gp.predict(x)]\n",
        "        sigma = np.sqrt(var).squeeze()\n",
        "        \n",
        "        ax[0].plot(x, y_pred, 'k', label=r'Predicted ($\\mu$)')\n",
        "        ax[0].plot(x, f(x), 'purple', label=r'Ground Truth ($f$)')\n",
        "        ax[0].set_xlabel(\"X\")\n",
        "        ax[0].set_ylabel(\"Gold content\")\n",
        "        ax[0].fill_between(x.flatten(), y_pred+sigma,\n",
        "                           y_pred-sigma, color='gray',\n",
        "                           alpha=alpha_plt, label=r'$\\mu \\pm \\sigma$')\n",
        "        ax[0].scatter(train_X, train_y, color='black', s=100,\n",
        "                      zorder=10, label='Training Points')\n",
        "        ax[0].legend(bbox_to_anchor=(1.04,1), loc=\"upper left\")\n",
        "        if str(acq_obj) == \"PI\":\n",
        "            ax[1].set_ylabel(r'$\\alpha_{PI}$')\n",
        "        elif str(acq_obj) == \"EI\":\n",
        "            ax[1].set_ylabel(r'$\\alpha_{EI}$')\n",
        "        elif str(acq_obj) == \"GP_UCB\":\n",
        "            ax[1].set_ylabel(r'$\\alpha_{GP-UCB}$')\n",
        "        elif str(acq_obj) == \"ACQ1\":\n",
        "            ax[1].set_ylabel(r'$\\alpha_{UCB}$')\n",
        "        elif str(acq_obj) == \"Rand\":\n",
        "            ax[1].set_ylabel(r'$\\alpha_{Random}$')\n",
        "        elif str(acq_obj) == \"EI_PI\":\n",
        "            ax[1].set_ylabel(r'$\\alpha_{EI-PI}$')\n",
        "        elif str(acq_obj) == \"Thompson\":\n",
        "            ax[1].set_ylabel(r'$\\alpha_{Thompson}$')\n",
        "        else:\n",
        "            raise ValueError\n",
        "          \n",
        "        if len(acq_params.keys()) == 0:\n",
        "            ax[0].set_title(f\"Iteration: {i}\")\n",
        "        elif 'eps' in acq_params.keys():\n",
        "            ax[0].set_title(f\"Iteration: {i} \\n\" + r\"$\\epsilon$ = \" + f\"{acq_params['eps']}\")\n",
        "            \n",
        "        elif 'lam' in acq_params.keys():\n",
        "            ax[0].set_title(f\"Iteration: {i} \\n\" + r\"$\\lambda$ = \" + f\"{acq_params['lam']}\")\n",
        "        \n",
        "        elif 'v' in acq_params.keys():\n",
        "            ax[0].set_title(f\"Iteration: {i} \\n\" + \\\n",
        "                            r\"$v$ = \" + str(acq_params['v']) + '\\n' \\\n",
        "                            r\"$\\delta$ = \" + str(acq_params['delta']))\n",
        "\n",
        "            \n",
        "        \n",
        "        # Choose the next point with highest sigma\n",
        "        max_mu = np.max(train_y).item()\n",
        "        max_till_now.append(max_mu)\n",
        "        next_ix = rargmax(acquisition)\n",
        "        next_x = x[next_ix]\n",
        "\n",
        "        # Add new point with highest uncertainty to the pool set\n",
        "        train_X = np.vstack([train_X, [x[next_ix]]])\n",
        "        train_y = f(train_X)\n",
        "\n",
        "        # Add new point with highest uncertainty to the pool set\n",
        "        ax[1].scatter(x[next_ix], acquisition[next_ix], marker='+',s=50,\n",
        "                      c='blue', zorder=10, label='Maxima')\n",
        "        ax[1].axvline(x = x[next_ix], color='black', lw=0.6, zorder=7, alpha=1)\n",
        "        ax[1].axhline(y = acquisition[next_ix], color='black', lw=0.6, zorder=7, alpha=1)\n",
        "        ax[0].scatter(x[next_ix], f(x[next_ix]), color='red',\n",
        "                      s=200, zorder=1, label='Query Point')\n",
        "        ax[0].legend(bbox_to_anchor=(1.1,1), loc=\"upper left\")\n",
        "        ax[1].legend(bbox_to_anchor=(1.1,0.5), loc=\"upper left\")\n",
        "        train_X = np.vstack([train_X, [x[next_ix]]])\n",
        "        train_y = f(train_X)\n",
        "        format_axes(ax[0])\n",
        "        format_axes(ax[1])\n",
        "\n",
        "        acq_params_str = '-'.join(list(map(str, acq_params.values())))\n",
        "        dirName = './MAB_pngs/'+str(acq_obj)+'/'+acq_params_str\n",
        "        os.makedirs(dirName, exist_ok=True)\n",
        "        plt.savefig(f\"{dirName}/{i}.png\", bbox_inches=\"tight\", dpi=180)\n",
        "        plt.close()\n",
        "    return dirName, max_till_now"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "3LV0DMhUv06k",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "train_X = np.atleast_2d([0.5]).T\n",
        "train_y = f(train_X)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "gWJMTFDAv06r",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "nnsvm(plt.rcParams, 2)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "fNUCOXhev06w",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# acq_params = {'lam': 0.01}\n",
        "\n",
        "# dirName, _ = plot_acquisition(\n",
        "#     train_X, train_y,\n",
        "#     ACQ1, val, acq_params,\n",
        "#     ylim1=(1, 10)\n",
        "# )\n",
        "\n",
        "# # gify\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-{acq_params['lam']}.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mOhL8oITv060",
        "colab_type": "text"
      },
      "source": [
        "Let us now try different hyperparameters for `ACQ1`. We can see that on increasing `lam` we \"explore\" more! In the below case we can easily see since we didn't give too much importance to the uncertainty (low `lam`) we got stuck in local minima."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iDlgkOFTv061",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/mab-gp-1.gif)"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "WQJg7_rsv062",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# acq_params = {'lam': 0.3}\n",
        "\n",
        "# dirName, _ = plot_acquisition(\n",
        "#     train_X, train_y,\n",
        "#     ACQ1,  val, acq_params,\n",
        "#     ylim1=(1, 10)\n",
        "# )\n",
        "\n",
        "# # gify\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-{acq_params['lam']}.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hsR1oJPJv066",
        "colab_type": "text"
      },
      "source": [
        "Below we can see that this choice of `lam = 5` is still a little smaller than we like (we would like to see that we get to exploit the location where the gold is the most.).\n",
        "\n",
        "![](MAB_gifs/mab-gp-5.gif)"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "OULGFUpMv067",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "acq_params = {'lam': 3}\n",
        "\n",
        "dirName, mtn_acq = plot_acquisition(\n",
        "    train_X, train_y,\n",
        "    ACQ1,  val, acq_params,\n",
        "    ylim1=(1, 14)\n",
        ")\n",
        "\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-{acq_params['lam']}.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "q3kfCubvv07B",
        "colab_type": "text"
      },
      "source": [
        "Perfect! We see that setting this value of `lam = 10` resulted in finding points near the global maxima and not getting stuck in a local maximum.\n",
        "\n",
        "![](MAB_gifs/mab-gp-10.gif)\n",
        "\n",
        "---\n",
        "\n",
        "### Random\n",
        "We had used a little intelligent acquisition function earlier, let's see is out acquisition function is not that intelligent and chooses randomly."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "g8VuTyN6v07C",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "class Rand(ACQ):\n",
        "    def acq_fn(\n",
        "        self, gp, x, **kwargs):\n",
        "        \"\"\"\n",
        "        gp: sklearn.GPRegresssor\n",
        "        \"\"\"\n",
        "        return np.random.uniform(size=x.shape[0])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "J-ks9W1iv07H",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# http://www.johndcook.com/blog/standard_deviation/\n",
        "# https://github.com/joschu/modular_rl/blob/master/modular_rl/running_stat.py\n",
        "\n",
        "class RunningStat(object):\n",
        "    def __init__(self, shape):\n",
        "        self._n = 0\n",
        "        self._M = np.zeros(shape)\n",
        "        self._S = np.zeros(shape)\n",
        "    def push(self, x):\n",
        "        x = np.asarray(x)\n",
        "        assert x.shape == self._M.shape\n",
        "        self._n += 1\n",
        "        if self._n == 1:\n",
        "            self._M[...] = x\n",
        "        else:\n",
        "            oldM = self._M.copy()\n",
        "            self._M[...] = oldM + (x - oldM)/self._n\n",
        "            self._S[...] = self._S + (x - oldM)*(x - self._M)\n",
        "    @property\n",
        "    def n(self):\n",
        "        return self._n\n",
        "    @property\n",
        "    def mean(self):\n",
        "        return self._M\n",
        "    @property\n",
        "    def var(self):\n",
        "        return self._S/(self._n - 1) if self._n > 1 else np.square(self._M)\n",
        "    @property\n",
        "    def std(self):\n",
        "        return np.sqrt(self.var)\n",
        "    @property\n",
        "    def shape(self):\n",
        "        return self._M.shape"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "g0DlD3Dyv07L",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "stats = RunningStat(len(mtn_acq))\n",
        "for seed in range(10):\n",
        "    dirName, temp = plot_acquisition(\n",
        "        train_X, train_y,\n",
        "        Rand,val,  ylim1=(0, 1.2),\n",
        "        seed=seed\n",
        "    )\n",
        "    stats.push(temp)\n",
        "\n",
        "mtn_rand = stats.mean\n",
        "rand_sig = stats.std\n",
        "\n",
        "# gify\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/rand.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5RllewTuv07Q",
        "colab_type": "text"
      },
      "source": [
        "We have here implemented a random method as a baseline. Notice, random method can find a location near the global maximum but is not able to exploit (try to find the global maxima that might be near this \"best\" location). Instead, it randomly chooses to explore (not even intelligently) here and there. Even with no intelligent, we might get good locations which might be close to the location with the most gold content.\n",
        "\n",
        "![](MAB_gifs/rand.gif)\n",
        "\n",
        "---\n",
        "\n",
        "### Probability of Improvement (PI)\n",
        "\n",
        "Let us look into our next method for the MAB maximisation problem. As before, we want to balance or trade-off between exploration and exploitation. The idea behind the algorithm is fairly simple - choose the next point as the one which has the highest probability of improvement over the current max ($\\mu^+$).\n",
        "\n",
        "\n",
        "Let's understand this concept via two cases:\n",
        "\n",
        "1. We have two points of similar means (of function values (gold in our case)). We now want to choose one of these to obtain the labels or values. We will choose the one with higher variance. This basically says that given same exploitability, we choose the one with higher exploration value.\n",
        "2. We have two points having same variance. We would now choose the point with the higher mean. This basically says that given same explorability, we will choose the one with higher exploitation value.\n",
        "\n",
        "\n",
        "1. Let $\\mu^+$ be the current highest value of the function\n",
        "2. Let $\\epsilon$ be close to zero\n",
        "3. Choose $x^* = arg\\,max(P(f(x)) > (\\mu^+ +\\epsilon))$\n",
        "\n",
        "This can be given as: $x^* = _{arg\\,max_{x}} \\Phi(\\frac{\\mu(x) - \\mu^+ - \\epsilon}{\\sigma(x)})$ where\n",
        "$\\Phi(.)$ indicates the CDF."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "aCWcYP17v07R",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# Code is heavily borrowed from: https://modal-python.readthedocs.io/\n",
        "# en/latest/_modules/modAL/acquisition.html#max_PI\n",
        "class PI(ACQ):\n",
        "    def acq_fn(\n",
        "        self, gp, x, mu=5., eps=0.01, **kwargs):\n",
        "        \"\"\"\n",
        "        gp: sklearn.GPRegresssor\n",
        "        \"\"\"\n",
        "        y_pred, var = [t.flatten() for t in gp.predict(x)]\n",
        "        sigma = np.sqrt(var).squeeze()\n",
        "        \n",
        "        cdf = ndtr((y_pred - mu - eps)/sigma)\n",
        "        return cdf"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "82EUrOmIv07V",
        "colab_type": "text"
      },
      "source": [
        "#### Intuition behind PI\n",
        "\n",
        "Below is a graph that helps to visualize how the PI values are calculated. We have calculated for 3 points `x in [0.10, 0.6, 4]`. We can see the CDF being shaded in the graphs below. Further, we can see if we increase `eps`, we implicitly place more importance to the uncertainty of a point. If `eps` is increased, the points with a larger sigma will benefit as their probability density is spread more. Thus points with more spread out sigma would have a higher value of cumulative density function on same $\\mu^+ + \\epsilon$."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ueX6QlScv07W",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/pi_cdf.gif)\n",
        "`mu_plus` refers to the maximum functional value i.e., `max(train_y)`, where `train_y` refers to the gold content at the currently drilled locations. We see that the probability of improvement values are calculated by finding the functional value of the cumulative density function at `mu_plus`. The Gaussian parameters for each point are the mean and standard deviation predicted from Gaussian Process Regressor for that point.\n",
        "\n",
        "#### Hyperparameter: Eps\n",
        "Now we have the intuition behind how Probability of Improvement is calculated, now let's change `eps` and look at its effects."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "RZmcL4nqv07X",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "train_X = np.atleast_2d([0.5]).T\n",
        "train_y = f(train_X).ravel()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "n44v1GdVv07c",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "eps = 0.075\n",
        "acq_params = {\n",
        "    'eps': eps\n",
        "}\n",
        "\n",
        "dirName, mtn_pi = plot_acquisition(\n",
        "    train_X, train_y,\n",
        "    PI, val,\n",
        "    acq_params = acq_params,\n",
        "    ylim1=((-.05, 1.05)),\n",
        ")\n",
        "\n",
        "# gify\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-pi-eps{eps}.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8T0oN0iNv07h",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/mab-gp-pi-eps0.01.gif)\n",
        "\n",
        "Looking at the graph above we can see that we are not effectively exploring at value `eps = 0.01` for the Probability of Improvement acquisition function. We are stuck."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "NxNc5OR-v07i",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "eps = 0.3\n",
        "acq_params = {\n",
        "    'eps': eps\n",
        "}\n",
        "\n",
        "dirName, _ = plot_acquisition(\n",
        "    train_X, train_y,\n",
        "    PI, val,\n",
        "    acq_params = acq_params,\n",
        "    ylim1=((-.05, 1)),\n",
        "    y1scale='log'\n",
        ")\n",
        "\n",
        "# gify\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-pi-eps{eps}.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fdJZY3r-v07o",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/mab-gp-pi-eps0.5.gif)\n",
        "\n",
        "Looking above, we see increasing the value to `eps = 0.5` enables us to explore more and get to the maximum value which we wanted in the case of Multi-arm bandit problem. One can notice that values at $x \\in [3, 4.5]$ posses uncertainty (can be identified by the grey translucent area, but as we remember we are not interested in getting the best prediction of the gold distribution, we only care about the maximum value that we can achieve, which this acquisition function with given hyper-parameters is able to capture nicely!\n",
        "\n",
        "Let's look at what happens if we increase the hyper-parameter `eps` a bit more."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "dVURNN-pv07p",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "eps = 3\n",
        "acq_params = {\n",
        "    'eps': eps\n",
        "}\n",
        "\n",
        "dirName, _ = plot_acquisition(\n",
        "    train_X, train_y,\n",
        "    PI, val,\n",
        "    acq_params = acq_params,\n",
        "    ylim1=((-.05, 1.05)),\n",
        "    y1scale='log'\n",
        ")\n",
        "\n",
        "# gify\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-pi-eps{eps}.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "43CFwWnvv07u",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/mab-gp-pi-eps3.gif)\n",
        "\n",
        "We see that we made things worse! Our model now uses `eps = 3` which has effectively resulted in way too much exploratoration. This amount of exploration is not able to exploit when we land somewhere near a global maximum.\n",
        "\n",
        "----\n",
        "\n",
        "### Expected Improvement (EI)\n",
        "\n",
        "Probability of improvement only looked at `how likely` is an improvement, but, shouldn't we be looking into `how much` we can improve. The next criterion called Expected Improvement (EI). It looks into both :)\n",
        "\n",
        "\\begin{equation}\n",
        "    EI(x)=\n",
        "    \\begin{cases}\n",
        "      (\\mu(x) - \\mu^+ - \\epsilon)\\Phi(Z) + \\sigma(x)\\phi(Z), & \\text{if}\\ \\sigma(x) > 0 \\\\\n",
        "      0 & \\text{if}\\ \\sigma(x) = 0 \n",
        "    \\end{cases}\n",
        "  \\end{equation}\n",
        " $$Z= \\frac{\\mu(x) - \\mu^+ - \\epsilon}{\\sigma(x)}$$\n",
        " where $\\Phi(.)$ indicates CDF and $\\phi(.)$ indicates pdf\n",
        " \n",
        "\n",
        "We can see when our _Expected Improvement_ will be high.\n",
        "\n",
        "- It is high when the expected value of mean(x) - $\\mu^+$ is high.\n",
        "- It is high when the uncertainty around a point is high.\n",
        "\n",
        "Now, if we see the role of $\\epsilon$ in _Expected Improvement_, it is the exact same as the role played in the case of _Probability of Improvement_ (we have the same expression in PI). -- footnotes You can know more about EI from here (https://thuijskens.github.io/2016/12/29/bayesian-optimisation/)"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "DdFFG-uyv07v",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "class EI(ACQ):\n",
        "    def acq_fn(self, gp, x, mu=5., eps=0.01, **kwargs):\n",
        "        \"\"\"\n",
        "        gp: sklearn.GPRegresssor\n",
        "        mu: max value of y among the selected train_pts\n",
        "        \"\"\"\n",
        "        y_pred, var = [t.flatten() for t in gp.predict(x)]\n",
        "        sigma = np.sqrt(var).squeeze()\n",
        "        z = (y_pred - mu - eps)/sigma\n",
        "        return (y_pred - mu - eps)*ndtr(z) + sigma*norm.pdf(z)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "scrolled": true,
        "id": "YjegrUOHv070",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# p = EI()\n",
        "# gp = gp_creator(train_X, train_y)\n",
        "# mu_plus = max(train_y)\n",
        "# plt.plot(x, 5*p(gp, x, mu=mu_plus, eps=0))\n",
        "# plt.ylabel(f\"{p}\")\n",
        "# plt.xlabel(\"X\");"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "scrolled": true,
        "id": "4iOMC16gv074",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# eps = 0\n",
        "# acq_params = {\n",
        "#     'eps': eps\n",
        "# }\n",
        "\n",
        "# dirName, _ = plot_acquisition(\n",
        "#     train_X, train_y,\n",
        "#     EI, val,\n",
        "#     acq_params=acq_params,\n",
        "#     ylim1=((-0.001, .3))\n",
        "# )\n",
        "\n",
        "# # gify!\n",
        "# # !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-ei-eps{eps}.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "mZM4sF_4v079",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# EI()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "U0toqm44v08E",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "eps = 0.01\n",
        "acq_params = {\n",
        "    'eps': eps\n",
        "}\n",
        "\n",
        "dirName, mtn_ei = plot_acquisition(\n",
        "    train_X, train_y,\n",
        "    EI, val,\n",
        "    acq_params=acq_params,\n",
        "    ylim1=((-0.001, .3))\n",
        ")\n",
        "\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-ei-eps{eps}.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iZVaQ6XKv08J",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/mab-gp-ei-eps0.01.gif)\n",
        "\n",
        "Like the Probability of Improvement's acquisition function, we can moderate the amount of explorability the Expected Improvement's acquisition function by setting the `eps` hyper-parameter. \n",
        "\n",
        "We see that having `eps = 0.01` primarily results in exploitation, and we are not able to get to the global maxima due to this myopic drilling location selection.\n",
        "\n",
        "Let's try increasing the `eps` variable to focus a little more on exploribility."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "LynEWVD6v08K",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# fig, ax = plt.subplots(nrows=2, sharex=True)\n",
        "# ax[0].plot(x, f(x))\n",
        "# ax[0].scatter([0.5], f(0.5))\n",
        "# ax[1].plot(np.linspace(0, 6, 600),ei)\n",
        "# ax[1].set_ylim((0, 0.35))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "JqctmL3Rv08R",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "eps = 0.3\n",
        "acq_params = {\n",
        "    'eps': eps\n",
        "}\n",
        "\n",
        "dirName, _ = plot_acquisition(\n",
        "    train_X, train_y,\n",
        "    EI,  val, \n",
        "    ylim1=((-0.001, .3)),\n",
        "    acq_params=acq_params,\n",
        ")\n",
        "\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-ei-eps{eps}.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sKSicl6Ev08Y",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/mab-gp-ei-eps1.5.gif)\n",
        "\n",
        "As we expected, increasing the value to `eps = 1.5` makes the acquisition function explore more and exploit when the time comes. We see that it moves slowly once it reaches near the global maxima, trying to find the global maxima. In this case, the exploration is effectively helping us reach a higher functional value much earlier!\n",
        "\n",
        "Let's see if increasing `eps` helps us more!"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "tkQPWI7Xv08Z",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "eps = 3\n",
        "acq_params = {\n",
        "    'eps': eps\n",
        "}\n",
        "\n",
        "dirName, _ = plot_acquisition(\n",
        "    train_X, train_y,\n",
        "    EI,  val, ylim1=((-0.001, .05)),\n",
        "    acq_params=acq_params,\n",
        "    y1scale='log'\n",
        ")\n",
        "\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-ei-eps{eps}.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RC6QHCtGv08e",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/mab-gp-ei-eps3.gif)\n",
        "\n",
        "Is this better than before? Turns out a yes and a no. We see that here we do too much exploration given the value of `eps = 3`. Which results in early reaching something close to global maxima, but unfortunately we don't exploit to get more gains near the global maxima. We would have liked an acquisition function that tried to exploit a bit more after reaching somewhere close to the global maxima. In essence:\n",
        "- reach near global maxima in a lower number of iterations\n",
        "- we don't exploit once we reach near global maxima\n",
        "\n",
        "---"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "V8lYkjhjv08f",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def ei_pi_plot(\n",
        "        train_X, train_y, i,  val,\n",
        "        ylim=None, xlim=None,\n",
        "        yscale='log', xscale='log',\n",
        "        seed = 2,\n",
        "        pi_params={}, ei_params={},\n",
        "    ):\n",
        "    np.random.seed(seed)\n",
        "    \n",
        "    fig, ax = plt.subplots()\n",
        "    ax.set_yscale(yscale)\n",
        "    ax.set_xscale(xscale)\n",
        "\n",
        "    # fitting\n",
        "    gp = gp_creator(train_X, train_y.flatten(),  val)\n",
        "\n",
        "    # for plotting scatter plot\n",
        "    pi_obj = PI()\n",
        "    ei_obj = EI()\n",
        "    pi_acq = pi_obj(gp, x, **pi_params, mu=max(train_y))\n",
        "    ei_acq = ei_obj(gp, x, **ei_params, mu=max(train_y))\n",
        "    \n",
        "    # predict on current pool set\n",
        "    y_pred, var = [t.flatten() for t in gp.predict(x)]\n",
        "    sigma = np.sqrt(var).squeeze()\n",
        "    \n",
        "    ax.scatter(pi_acq, ei_acq, color='black', alpha=0.4, label='Location')\n",
        "    ax.set_ylabel(r'$\\alpha_{EI}$')\n",
        "    ax.set_xlabel(r'$\\alpha_{PI}$')\n",
        "    \n",
        "    # limits\n",
        "    ax.set_ylim(ylim) if ylim is not None else None # ax.set_ylim(min(ei_acq), max(ei_acq)\n",
        "    ax.set_xlim(xlim) if xlim is not None else None # ax.set_xlim(min(pi_acq), max(pi_acq))\n",
        "    \n",
        "    ax.set_title(r'$\\epsilon_{PI} = $' + str(pi_params['eps']) + '\\n' \\\n",
        "                 r'$\\epsilon_{EI} = $' + str(ei_params['eps']))\n",
        "\n",
        "    ax.legend(bbox_to_anchor=(1.1,1), loc=\"upper left\")\n",
        "    format_axes(ax)\n",
        "    \n",
        "    pii = list(map(str, pi_params.values()))\n",
        "    eii = list(map(str, ei_params.values()))\n",
        "    params_str = '-'.join(pii + eii)\n",
        "    dirName = './MAB_gifs/Ei_Pi_graph/'\n",
        "    os.makedirs(dirName, exist_ok=True)\n",
        "    plt.savefig(f\"{dirName}/{i}.svg\", bbox_inches=\"tight\")\n",
        "    plt.close()\n",
        "    return (dirName)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "oZBPuE4lv08k",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "pi_eps = [0.01]#, 0.001, 0.01, 0.1, 1]\n",
        "ei_eps = [0.01]#, 0.001, 0.01, 0.1, 1]\n",
        "\n",
        "for i, (pi_ep, ei_ep) in enumerate(zip(pi_eps, ei_eps)):\n",
        "    pi_params = {'eps': pi_ep}\n",
        "    ei_params = {'eps': ei_ep}\n",
        "\n",
        "    dirName = ei_pi_plot(\n",
        "        train_X, train_y, i, val,\n",
        "        pi_params = pi_params,\n",
        "        ei_params = ei_params,\n",
        "        yscale='linear', xscale='linear',\n",
        "        ylim=None, xlim=(0, .5),\n",
        "    )\n",
        "\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/ei_pi_graph.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yAY5lWiov08o",
        "colab_type": "text"
      },
      "source": [
        "We have seen two closely related methods, The _Probability of Improvement_ and the _Expected Improvement_. We can think of these two to be related to the ideas that we commonly are familiar with `risk` and `gain` respectively.\n",
        "\n",
        "It seems natural to see how these metrics change for each of the points. We have plotted the values for both policies' acquisition function's values below, for each of the possible locations. The graph shows the relation followed between EI and PI for when we have a single training point `(0.5 f(0.5))`. \n",
        "\n",
        "![](MAB_gifs/Ei_Pi_graph/0.svg)\n",
        "\n",
        "If we look closely, we can see if we have an equal estimated improvement as in the case with the points with `EI(x) = 0.4` it would be more beneficial to differentiate between these points which have a better value for Probability of Improvement. In other words, when `gain`s are the same, we should prioritize to choose the option with lesser `risk`. And similarly, when the `risk`s are similar, we would likely want to go with points with greater `gain`s.\n",
        "\n",
        "---\n",
        "\n",
        "### Gaussian Process Upper Confidence Bound (GP_UCB)\n",
        "\n",
        "GP_UCB is another formulation for acquisition function where we also have theoretical bounds on the number of iterations taken to reach near global maximum."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "BtbbOVsav08p",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "class GP_UCB(ACQ):\n",
        "    def acq_fn(self, gp, x, t, mu=5.,\n",
        "               v = 1., delta=1., **kwargs):\n",
        "        \"\"\"\n",
        "        gp: sklearn.GPRegresssor\n",
        "        t: Iteration number (1, ..)\n",
        "        \"\"\"\n",
        "        d = x.shape[1]\n",
        "        y_pred, var = [t.flatten() for t in gp.predict(x)]\n",
        "        sigma = np.sqrt(var).squeeze()\n",
        "#         print (y_pred, sigma)\n",
        "#         print ('blah', np.log( (t**(d/2. + 2))*(np.pi**2)/(3. * delta)  ))\n",
        "#         print ('v3', v* (2*  np.log( (t**(d/2. + 2))*(np.pi**2)/(3. * delta)  )))\n",
        "        k = np.sqrt( v* (2*  np.log( (t**(d/2. + 2))*(np.pi**2)/(3. * delta)  )))\n",
        "#         print ('k', k)\n",
        "        return y_pred + k*sigma"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "yywuTbHVv08u",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "acq_params = {\n",
        "    'v': 1,\n",
        "    'delta': 1\n",
        "}\n",
        "\n",
        "dirName, mtn_gp_ucb = plot_acquisition(\n",
        "    train_X, train_y,\n",
        "    GP_UCB, val, ylim1=((0, 10)),\n",
        "    acq_params=acq_params,\n",
        ")\n",
        "\n",
        "# # gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-gp_ucb1-1.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "J72W-pd_v081",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/mab-gp-gp_ucb1-1.gif)\n",
        "\n",
        "We seem to be exploiting too much , let's increase the exploratory hyperparameters!"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "eSpfqWDyv082",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# acq_params = {\n",
        "#     'v': 3,\n",
        "#     'delta': 1\n",
        "# }\n",
        "\n",
        "# dirName, mtn_gp_ucb = plot_acquisition(\n",
        "#     train_X, train_y,\n",
        "#     GP_UCB, val, ylim1=((0, 10)),\n",
        "#     acq_params=acq_params,\n",
        "# )\n",
        "\n",
        "# # gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-gp_ucb3-1.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6BZ5EB9dv08_",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/mab-gp-gp_ucb3-1.gif)\n",
        "\n",
        "Using this set of hyper-parameters, we are able to get near global maxima and further \"exploit\" to find the global maximum. This was a result of increasing the value of `v` to `3`; this shows that `v` gives weightage to exploration."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ivQ1BpQ7v09A",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# acq_params = {\n",
        "#     'v': 1,\n",
        "#     'delta': 3\n",
        "# }\n",
        "\n",
        "# dirName, _ = plot_acquisition(\n",
        "#     train_X, train_y,\n",
        "#     GP_UCB, val, ylim1=((0, 10)),\n",
        "#     acq_params=acq_params,\n",
        "# )\n",
        "\n",
        "# # gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-gp_ucb1-3.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2ih04ScCv09I",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/mab-gp-gp_ucb1-3.gif)\n",
        "\n",
        "Setting the values of the hyperparameters  to `v = 1` and `delta = 3` results a greater exploitation."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HkASAjsnv09K",
        "colab_type": "text"
      },
      "source": [
        "---\n",
        "\n",
        "### Thompson Sampling\n",
        "One more acquisition function that is quite common is Thompson Sampling. It has a low overhead of setting up.\n",
        "\n",
        "The idea is to sample functions within upper and lower probabilistic bounds of a regressor; one can then optimize on these functions and chose the next query point to be the $\\boldsymbol{x} = argmax(\\texttt{sampled_f})$. In other words, sampling functions within the greyed out area in the graph below.\n",
        "\n",
        "![](MAB_gifs/posterior.svg)\n",
        "\n",
        "Thompson Sampling is general enough to be useful even when we have Bernoulli (the domain of x is spatially independent) distributions modeling the function $F$, instead of Gaussian Process."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "9veLZ9hkv09L",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "class Thompson(ACQ):\n",
        "    def acq_fn(self, gp, x, mu=5., **kwargs):\n",
        "        \"\"\"\n",
        "        gp: sklearn.GPRegresssor\n",
        "        mu: max value of y among the selected train_pts\n",
        "        x: domain in which we are optimizing\n",
        "        \"\"\"\n",
        "        sampled_y = gp.posterior_samples_f(x, size=1)\n",
        "#         print(sampled_y.shape)\n",
        "        return sampled_y.flatten()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "GqMr5vuAv09U",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        ""
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "wB4dCTYwv09X",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "dirName, mtn_th = plot_acquisition(\n",
        "    train_X, train_y,\n",
        "    Thompson, val, ylim1=((0, 8.5)),\n",
        ")\n",
        "\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/mab-gp-thomp.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6j6ZUeVPv09a",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/mab-gp-thomp.gif)\n",
        "\n",
        "---\n",
        "\n",
        "### Probability of Improvement + $\\lambda \\  \\times$ Expected Improvement (EI_PI)\n",
        "\n",
        "Below we have tried to combine PI and EI using a linear combination as a combination of various acquisition function also results in an acquisition function. We can, therefore, combine any of the acquisition function and form a new one."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "owiPsoRwv09b",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "class EI_PI(ACQ):\n",
        "    def acq_fn(self, gp, x, mu=5., eps_pi=0.01, eps_ei=0.01, lam=.3, **kwargs):\n",
        "        \"\"\"\n",
        "        gp: sklearn.GPRegresssor\n",
        "        \"\"\"\n",
        "        y_pred, var = [t.flatten() for t in gp.predict(x)]\n",
        "        sigma = np.sqrt(var).squeeze()\n",
        "        # ei\n",
        "        z = (y_pred - mu - eps_ei)/sigma\n",
        "        ei_acq = (y_pred - mu - eps_ei)*ndtr(z) + sigma*norm.pdf(z)\n",
        "        \n",
        "        # pi\n",
        "        pi_acq = ndtr((y_pred - mu - eps_pi)/sigma)\n",
        "        return pi_acq + lam * ei_acq"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XSsUN_9Bv09f",
        "colab_type": "text"
      },
      "source": [
        "### Comparison\n",
        "\n",
        "Below we have a graph showing a comparison between the methods discussed above. We have chosen the hyper-parameters that gave us the best performance during our basic hyper-parameter search.\n",
        "\n",
        "We see the _Random_ method is able to find the maximum much before any of the other methods, this can be seen when we are faced with smaller spaces to find the global maximum. If we have more dimensions to `x`, searching in this space would not be so easy using random, due to what we call the curse of dimensionality."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "AUy1cMVtv09g",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "nnsvm(plt.rcParams, 4)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "scrolled": false,
        "id": "y7p-ESyUv09l",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "mtns = [mtn_rand, mtn_acq, mtn_pi, mtn_ei, mtn_th, mtn_gp_ucb]\n",
        "names = ['Random', 'UCB', 'PI', 'EI', 'Thompson', 'GP-UCB']\n",
        "markers = ['o-', 'v--', '^-', '<:', '>-.', '8:']\n",
        "\n",
        "xx = range(len(mtns[0]))\n",
        "plt.figure(figsize=(14, 8))\n",
        "for n, m, mm in zip(names, mtns, markers):\n",
        "    plt.plot(xx, m, mm, label=n, alpha=0.7, lw=2.7)\n",
        "\n",
        "plt.legend()\n",
        "plt.xlabel('# of Drilled Sites')\n",
        "plt.ylabel('Max Gold Sensed')\n",
        "plt.title('Comparison of different Acquisition Functions on Gold Mining task')\n",
        "format_axes(plt.gca())\n",
        "plt.savefig(\"MAB_gifs/comp.svg\", bbox_inches=\"tight\")\n",
        "plt.show()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "M1XtquaHv09n",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/comp.svg)\n",
        "\n",
        "## Observation\n",
        "\n",
        "We see that the plots above show the maximum gold content detected for the case of multi-arm bandit problem vs. the number of holes drilled. Looking at the graph above we can see that for our problem Probability of Improvement performed the best among all the variants of Acquisition functions."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fYV7VOYCv09o",
        "colab_type": "text"
      },
      "source": [
        "# Generalization\n",
        "Let us now formally introduce Bayesian Optimization. Our goal is to find the $\\boldsymbol{x}$ where we reached global maximum (or minimum) of a function $F: \\mathbb{R}^d \\texttt{ -> } \\mathbb{R}$. Constraints in Bayesian Optimization look like below. -- footnotes Slides link to Peter Fraizer\n",
        "\n",
        "> We’d like to optimize $F: \\mathbb{R}^d \\texttt{ -> } \\mathbb{R}$,\n",
        "where $d < 20$.<br>\n",
        ">\n",
        ">• $F$’s feasible set $A$ is simple,\n",
        "e.g., box constraints.<br>\n",
        "• $F$ is continuous but lacks special structure,\n",
        "e.g., concavity, that would make it easy to optimize.<br>\n",
        "• $F$ is derivative-free:\n",
        "evaluations do not give gradient information.<br>\n",
        "• $F$ is expensive to evaluate:\n",
        "the # of times we can evaluate it\n",
        "is severely limited.<br>\n",
        "• $F$ may be noisy. If noise is present, we’ll assume it\n",
        "is independent and normally distributed, with\n",
        "common but unknown variance.<br>\n",
        "\n",
        "Let us link the above constraints to our initial problem statement of gold mining.\n",
        "\n",
        "- Our domain in the gold mining problem is a single dimensional box constraint of $0 \\leq x \\leq 6$.\n",
        "- Our ground truth can be seen as _not_ convex or concave function, which resulted in local minima as well.\n",
        "- Our evaluation (by drilling) of the amount of gold content at a location didn't give us any gradient information.\n",
        "- The function we used in the case of Gold Mining problem is extremely costly to evaluate (drilling costs millions).\n",
        "- This constraint is still satisfied in our case as we had used 0 noise, or zero mean zero std gaussian noise."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UV-KM67rv09o",
        "colab_type": "text"
      },
      "source": [
        "# Higher Dimensions\n",
        "\n",
        "For now we have been looking at real-valued single dimensional function, i.e. $f: \\mathbb{R} \\texttt{ -> } \\mathbb{R}$ data where we needed to find the value of $\\boldsymbol{x}$ where we reached global maximum. Let's move on and try to tackle real-valued functions of $n$ real variables functions, i.e. $f: \\mathbb{R}^n \\texttt{ -> } \\mathbb{R}$. We will soon see that our methods that we saw earlier for the single dimensional case can be easily ported to multi-variable functions.\n",
        "\n",
        "## Why is this easier?\n",
        "One valid question one might come up is that we have replaced the original optimization problem to another optimization problem (optimization of acquisition function). How is this any better than the last problem. The main reason is that evaluating the acquisition function is much cheaper, whereas in the original problem, evaluating the value at a particular was extremely costly.\n",
        "\n",
        "## Bayesian Optimization vs. Gradient Descent\n",
        "Some of the main differences between BO and GD --cite BOvsGD as pointed out at StackExchange:\n",
        " - The biggest difference between Bayesian Optimization and Gradient Descent is that in the latter case, we have access to the gradient values.\n",
        " - BO doesn't assume the function to be convex, in the case of Gradient Descent if you would like to get to the global minima, your function should be convex.\n",
        " - BO assumes the function we are optimizing is fairly smooth.\n",
        " - BO doesn't scale well with large data, as the GP inference is cubic in the number of points.\n",
        " \n",
        "Now, as we have described BO more technically, let's have a look at how we can use this method in the case of Hyperparameter Tuning. Hyperparameters, you ask?\n",
        "\n",
        "## Hyperparameters v/s Parameters\n",
        "\n",
        "We all are familiar with Machine Learning and the models that we use. To show one of the use cases for Bayesian optimization, we will quickly differentiate between hyperparameters and parameters. \n",
        "\n",
        "Hyperparameters is a parameter whose value is set before the learning process begins. Parameters, on the other hand, are the parameters that are learned looking at the data. One small example that we can think of can be of linear regression, we don't really have hyperparameters, but the parameters are the $W$: weight, $c$: intercept, which is learned from the data. If we apply lasso to linear regression, we introduce a regularization hyperparameter $\\lambda$. -- footnotes Wikipedia article\n",
        "\n",
        "## Hyperparameter Search\n",
        "\n",
        "Now as we are clear on the difference between hyperparameters and parameters we would like to introduce one of the most common use case of Bayesian Optimization; _Hyperparameter Tuning_: finding best performing hyperparameters on machine learning models. At last, hyperparameter searching is an optimization problem (optimizing our score).\n",
        "\n",
        "Usually, when training a model isn't expensive and time-consuming, we might just do a grid search or random search. The main issues faced when using these methods are as follows:\n",
        "\n",
        "- Grid Search is not feasible if getting the functional value is extremely costly, as in case of a large neural network that takes days to train. This might result in days of waiting to get the accuracy scores.\n",
        "- Random and Grid Search are near brute-force methods; this causes these methods to become extremely inefficient and impossible to use when the dimensions of our search space increase (aka the curse of dimensionality). You can get the intuition for _the curse of dimentionality_ from [this](https://stats.stackexchange.com/a/169170) excellent answer on stackoverflow.com\n",
        "  > in essence when you have more dimensions you need exponentially more samples to be able to estimate the real-valued multivariable function.\n",
        "\n",
        "We turn to Bayesian optimization to find counter the expensiveness of getting the functional values, and these increased dimensions."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "P82VCdhGv09p",
        "colab_type": "text"
      },
      "source": [
        "### Example 1\n",
        "\n",
        "Let's us use a SVM on sklearn's moons dataset and try to find the optimal hyperparameter using bayesian optimization. Let's have a look at the dataset first."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "q5I5Qe_Dv09p",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "from sklearn.datasets import make_moons\n",
        "from sklearn.svm import SVC\n",
        "import matplotlib as mpl\n",
        "from sklearn.model_selection import train_test_split"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "B2mynQjHv09r",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "nnsvm(plt.rcParams, -2)\n",
        "uplow = (.40, .85)\n",
        "seed = 0\n",
        "X, y = make_moons(n_samples=500, shuffle=True, noise=1, random_state=seed)\n",
        "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=seed)\n",
        "\n",
        "mask = (y == 1)\n",
        "plt.scatter(X[mask][:, 0], X[mask][:, 1], label=\"Class A\")\n",
        "mask = (y == 0)\n",
        "plt.scatter(X[mask][:, 0], X[mask][:, 1], label=\"Class B\")\n",
        "plt.legend(loc='top right')\n",
        "plt.xlabel(\"X0\")\n",
        "plt.title('Moons Dataset')\n",
        "plt.ylabel(\"X1\")\n",
        "plt.savefig(\"MAB_gifs/moons.svg\", bbox_inches=\"tight\")\n",
        "plt.show()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "g8pKUmexv09t",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def f(Listofpoints, seed=0):\n",
        "    '''Given a grid of hyperparameters we fit the SVM and return\n",
        "    the negative of validation error (we are aiming to minimize error)'''\n",
        "    Listofpoints = np.array(Listofpoints)\n",
        "    Gammas, Cs = Listofpoints[:, 0], Listofpoints[:, 1]\n",
        "    Gammas = np.array(Gammas)\n",
        "    Cs = np.array(Cs)\n",
        "    shpe = Gammas.shape\n",
        "    assert (Gammas.shape == Cs.shape)\n",
        "    accs = []\n",
        "    for gamma, C in zip(Gammas.flatten(), Cs.flatten()):\n",
        "        clf = SVC(gamma=10**(gamma), C=C, random_state=seed)\n",
        "        clf.fit(X_train, y_train)\n",
        "        pred_y = clf.predict(X_test)\n",
        "        mask = (pred_y == y_test)\n",
        "        acc = mask.sum()/mask.shape[0]\n",
        "        accs.append(acc)\n",
        "    return np.array(accs).reshape(shpe)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "scrolled": true,
        "id": "7i_5KoUMv09u",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "x0 = np.linspace(-6, 3, 25) # gammas\n",
        "x1 = np.linspace(0.01, 9, 25) # Cs\n",
        "\n",
        "x = np.array(list(itertools.product(x0, x1)))\n",
        "print ('x.shape:', x.shape)\n",
        "\n",
        "X0, X1 = np.meshgrid(x0, x1)\n",
        "xx = np.vstack([X0.reshape(X0.size), X1.reshape(X1.size)]).T\n",
        "\n",
        "GT = f(x)\n",
        "GT = GT.reshape(X0.shape)\n",
        "print('GT.shape:', GT.shape)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "NZ1jnoG8v09w",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# kernel = Matern(length_scale=1.0)\n",
        "# gp = GaussianProcessRegressor(kernel=kernel, normalize_y=True)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "wyLYVdIRv09y",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def getPoints(a, b):\n",
        "    '''Returns points in meshgrid'''\n",
        "    pts = np.concatenate([a.flatten().reshape(-1, 1), \n",
        "                          b.flatten().reshape(-1, 1)], \n",
        "                         axis=1)\n",
        "    return pts"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "M5dIAvDzv09z",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "nnsvm(plt.rcParams, 4)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "av4ecfOCv090",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "from matplotlib.markers import MarkerStyle"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "RHpdbRzEv094",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# https://matplotlib.org/3.1.1/gallery/ticks_and_spines/colorbar_tick_labelling_demo.html\n",
        "import sys\n",
        "from copy import deepcopy\n",
        "\n",
        "def plot_acquisition3d(\n",
        "        acq_class, mean_fn, \n",
        "        acq_params={},\n",
        "        z0lim = (.50, 1.00),\n",
        "        it = 10, \n",
        "        seed = 0,\n",
        "        svm=False,\n",
        "        gran = 16,\n",
        "    ):\n",
        "    # init svm or rf\n",
        "    train_X = np.array([[5, 5]]) # gamma = 5, C = 10\n",
        "    train_y = f(train_X)\n",
        "    \n",
        "    \"\"\"acq_params would contain the parameters for our acq_fn\"\"\"\n",
        "    np.random.seed(seed)\n",
        "    # for storing the max till it iters\n",
        "    max_till_now = []\n",
        "    max_ix = np.argmax(GT.flatten())\n",
        "    max_x = X0.flatten()[max_ix], X1.flatten()[max_ix]\n",
        "    \n",
        "    save = {}\n",
        "    \n",
        "    # Saving stuff\n",
        "    z1lim = [sys.maxsize, - sys.maxsize]\n",
        "    for i in range(it):\n",
        "        # fitting\n",
        "        gp = gp_creator(train_X, train_y, val = mean_fn)\n",
        "        \n",
        "        acq_obj = acq_class()\n",
        "        acquisition = acq_obj(\n",
        "            gp, \n",
        "            xx, \n",
        "            t=i+1,\n",
        "            **acq_params, \n",
        "            mu=max(train_y)\n",
        "        )\n",
        "        \n",
        "        save[i] = {\n",
        "            'train_X': deepcopy(train_X),\n",
        "            'acquisition': deepcopy(acquisition),\n",
        "            'train_y': deepcopy(train_y),\n",
        "        }\n",
        "        \n",
        "        # Choose the next point with highest acq\n",
        "        max_mu = max(train_y.flatten())\n",
        "        max_till_now.append(max_mu)\n",
        "        next_ix = rargmax(acquisition.flatten())\n",
        "        n_x = X0.flatten()[next_ix], X1.flatten()[next_ix]\n",
        "\n",
        "        # Add new point with highest uncertainty to the pool set\n",
        "        train_X = np.vstack([train_X, [*n_x]])\n",
        "        train_y = f(train_X)\n",
        "        \n",
        "        save[i].update({\n",
        "            'n_x': deepcopy(n_x),            \n",
        "        })\n",
        "        \n",
        "        z1lim[0] = min(z1lim[0], min(acquisition))\n",
        "        z1lim[1] = max(z1lim[1], max(acquisition))  \n",
        "    \n",
        "    for i in range(it):\n",
        "        # getting things\n",
        "        train_X = save[i]['train_X']\n",
        "        acquisition = save[i]['acquisition']\n",
        "        train_y = save[i]['train_y']\n",
        "        n_x = save[i]['n_x']\n",
        "        \n",
        "        fig, ax = plt.subplots(ncols=2, figsize=(16, 6))\n",
        "        \n",
        "        for a in ax:\n",
        "            a.set_aspect('equal')\n",
        "        \n",
        "        ax[0].set_ylim(min(x[:, 1]), max(x[:, 1])) # C\n",
        "        ax[0].set_xlim(min(x[:, 0]), max(x[:, 0])) # gamma\n",
        "        ax[1].set_ylim(min(x[:, 1]), max(x[:, 1]))\n",
        "        ax[1].set_xlim(min(x[:, 0]), max(x[:, 0]))\n",
        "        \n",
        "        # plot ground truth\n",
        "        levels1 = ticks1 = np.linspace(*z0lim, gran)\n",
        "        h = ax[0].contourf(\n",
        "            X0, X1, GT, cmap='viridis', \n",
        "            levels=levels1, \n",
        "            vmin=z0lim[0], \n",
        "            vmax=z0lim[1]\n",
        "        )\n",
        "        plt.colorbar(\n",
        "            h, \n",
        "            ticks=ticks1,\n",
        "            ax=ax[0]\n",
        "        )\n",
        "        \n",
        "        # plot current training set\n",
        "        ax[0].scatter(\n",
        "            train_X[:, 0],\n",
        "            train_X[:, 1],\n",
        "            facecolors='none', \n",
        "            edgecolors='k',\n",
        "            linewidth=4,\n",
        "            s=100,\n",
        "            zorder=10,\n",
        "            label='Training points'\n",
        "        )\n",
        "        # plot the global maxima\n",
        "        ax[0].scatter(\n",
        "            *max_x,\n",
        "            marker=\"*\", \n",
        "            color='gold',\n",
        "            s=300,\n",
        "            zorder=20,\n",
        "            label=\"Global Maxima\"\n",
        "        )\n",
        "        \n",
        "        levels2 = ticks2 = list(np.linspace(*z1lim, gran))\n",
        "        h2 = ax[1].contourf(\n",
        "            X0, X1, \n",
        "            acquisition.reshape(X0.shape), \n",
        "            cmap='viridis', \n",
        "            levels=levels2, \n",
        "            vmin=z1lim[0],\n",
        "            vmax=z1lim[1],\n",
        "        )\n",
        "        # colorbar 1\n",
        "        cbar = fig.colorbar(\n",
        "            h2,\n",
        "            ticks=ticks2,\n",
        "            ax=ax[1]\n",
        "        )\n",
        "        ax[1].set_title(f\"Acquisition Function ({str(acq_obj)})\")\n",
        "        fig.suptitle(f\"Iteration: {i}\", fontsize='x-large')\n",
        "            \n",
        "        if svm:\n",
        "            ax[1].set_xlabel(r\"Hyperparam #1 ($\\log_{10}\\gamma$)\")\n",
        "            ax[1].set_ylabel(r\"Hyperparam #2 $(C)$\")\n",
        "            ax[0].set_xlabel(r\"Hyperparam #1 ($\\log_{10}\\gamma$)\")\n",
        "            ax[0].set_ylabel(r\"Hyperparam #2 $(C)$\")\n",
        "        else:\n",
        "            ax[1].set_xlabel(\"Hyperparam #1 (# of Trees)\")\n",
        "            ax[1].set_ylabel(\"Hyperparam #2 (Max Depth)\")\n",
        "            ax[0].set_xlabel(\"Hyperparam #1 (# of Trees)\")\n",
        "            ax[0].set_ylabel(\"Hyperparam #2 (Max Depth)\")\n",
        "\n",
        "        # Add new point with highest uncertainty to the pool set\n",
        "        ax[1].scatter(\n",
        "            *n_x, \n",
        "            marker='+',\n",
        "            s=200,\n",
        "            c='blue',\n",
        "            zorder=10,\n",
        "            label='Maxima'\n",
        "        )\n",
        "        ax[0].scatter(*n_x, color='red',\n",
        "                      s=200, zorder=1, label='Query Point')\n",
        "        ax[0].axvline(n_x[0], color='black', lw=1, zorder=1)\n",
        "        ax[0].axhline(n_x[1], color='black', lw=1, zorder=1)\n",
        "        ax[1].axvline(n_x[0], color='black', lw=1, zorder=1)\n",
        "        ax[1].axhline(n_x[1], color='black', lw=1, zorder=1)\n",
        "        ax[0].set_title(\"Accuracy\")\n",
        "        \n",
        "        # https://stackoverflow.com/questions/4700614/how-to-put-the-legend-out-of-the-plot\n",
        "        for a in ax:\n",
        "            box = a.get_position()\n",
        "            a.set_position([box.x0, box.y0 + box.height * 0.02,\n",
        "                             box.width, box.height * 0.9])\n",
        "\n",
        "            # Put a legend below current axis\n",
        "            a.legend(loc='upper center', bbox_to_anchor=(0.5, -0.15),\n",
        "                      fancybox=True, shadow=True, ncol=5)\n",
        "        acq_params_str = '-'.join(\n",
        "            list(map(str, acq_params.values()))\n",
        "        )\n",
        "        if svm:\n",
        "            lala = ''\n",
        "        else:\n",
        "            lala = 'RF'\n",
        "        dirName = './MAB_pngs/'+lala+str(acq_obj)+'3d/'+acq_params_str\n",
        "        os.makedirs(dirName, exist_ok=True)\n",
        "        plt.savefig(\n",
        "            f\"{dirName}/{i}.png\",\n",
        "            bbox_inches=\"tight\", \n",
        "        )\n",
        "        fig.clear();\n",
        "        plt.close()\n",
        "    return dirName, max_till_now"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fc3IDVmvv097",
        "colab_type": "text"
      },
      "source": [
        "Let us now show the some plots showing Bayesian Optimization for learning nice hyperparameters for our Support Vector Machine model. -- footnotes Note: the surface plots you see for the ground truth accuracies were calculated for each possible of hyperparameter for showcasing purposes."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "scrolled": false,
        "id": "fqpqTAQdv098",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "eps = 0.05\n",
        "acq_params = {'eps':eps}\n",
        "dirName, mtn_pi = plot_acquisition3d(\n",
        "    PI, mean_fn = 0.65, \n",
        "    acq_params=acq_params,\n",
        "    z0lim=uplow,\n",
        "    it=10, svm=True\n",
        ")\n",
        "\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/pi3d-{eps}-mat.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YzRPiyOvv099",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/pi3d-0.05-mat.gif)\n",
        "\n",
        "Above we see a gif showing the work of teh _Probability of Improvement_ acquisition function in finding the best hyperparameters."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "uuVbyNt_v09-",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "eps = 0.01\n",
        "acq_params = {'eps':eps}\n",
        "dirName, mtn_ei = plot_acquisition3d(\n",
        "    EI, mean_fn = 0.65, \n",
        "    acq_params=acq_params,\n",
        "    z0lim=uplow,\n",
        "    it=10, svm=True\n",
        ")\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/ei3d-{eps}-mat.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "d414vfWmv09_",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/ei3d-0.0001-mat.gif)\n",
        "\n",
        "Above we see a gif showing the work of teh _Expected Improvement_ acquisition function in finding the best hyperparameters."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "scrolled": true,
        "id": "P7iGOlIMv09_",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "v = 1\n",
        "delta = 2\n",
        "acq_params = {\n",
        "    'v':v,\n",
        "    'delta':delta\n",
        "}\n",
        "dirName, mtn_gp_ucb = plot_acquisition3d(\n",
        "    GP_UCB, mean_fn = 0.65, \n",
        "    acq_params=acq_params,\n",
        "    z0lim=uplow,\n",
        "    it=10, svm=True\n",
        ")\n",
        "\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/gp3d-1-2-mat.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9VV6N0aov0-B",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/gp3d-1-2-mat.gif)\n",
        "\n",
        "Above we see a gif showing the work of the _Guassian Processes Upper Confidence Bound_ acquisition function in finding the best hyperparameters. This by far seems to perform the best with getting quite close to the global optimum value of hyperparameters (found using brute force)."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "KkhvIejxv0-C",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "stats = RunningStat(len(mtn_gp_ucb))\n",
        "for seed in range(10):\n",
        "    dirName, temp = plot_acquisition3d(\n",
        "        Rand, mean_fn = 0.65, \n",
        "        acq_params=acq_params,\n",
        "        z0lim=uplow,\n",
        "        seed=seed,\n",
        "        it=10, svm=True\n",
        "    )\n",
        "    stats.push(temp)\n",
        "\n",
        "mtn_rand = stats.mean\n",
        "rand_sig = stats.std\n",
        "\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/rand3d.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jqKMo8IPv0-F",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/rand3d.gif)\n",
        "\n",
        "Now our favourite random acquisition function. :)"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "G-i2-f33v0-F",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "nnsvm(plt.rcParams, 4)\n",
        "max_f = np.max(GT.flatten())\n",
        "globalopt = [max_f] * len(mtn_rand)\n",
        "\n",
        "mtns = [mtn_rand, globalopt, mtn_pi, mtn_ei, mtn_gp_ucb]\n",
        "names = ['Random', 'GlobalOpt', 'Probability of Improvement', 'Expected Improvement', 'GP-UCB']\n",
        "markers = ['o-', '*:', 'v--', '>-.', '<:']\n",
        "\n",
        "xx = range(len(mtns[0]))\n",
        "plt.figure(figsize=(14, 8))\n",
        "for n, m, mm in zip(names, mtns, markers):\n",
        "    plt.plot(xx, m, mm, label=n, alpha=0.7, lw=2.7)\n",
        "\n",
        "plt.legend()\n",
        "plt.xlabel('# of Evaluations')\n",
        "plt.ylabel('Max Accuracy Reached')\n",
        "plt.title('Comparison of different Acquisition Functions')\n",
        "format_axes(plt.gca())\n",
        "plt.savefig(\"MAB_gifs/comp3d.svg\", bbox_inches=\"tight\")\n",
        "plt.close()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "O8cfQRJav0-H",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/comp3d.svg)\n",
        "\n",
        "We see GP-UCB performed the best in this case. Random that was performing really nice in the last single dimensional example seems to perform much badly in this case. This can be attributed to the increase in the number of dimensions, it's much difficult to get to the optimal value by using random search."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7B2V9PM2v0-I",
        "colab_type": "text"
      },
      "source": [
        "### Example 2\n",
        "\n",
        "Let's train CNNs on Mnist. Here we will be using `scikit-optim`, which also provides us support for optimizing our function on a mix of categorical, integral, and real variables. We won't be plotting the ground truth here, as it's extremely costly to do so."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Y9dyRYKNv0-I",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "# # getting the data\n",
        "# import torchvision\n",
        "# import torchvision.datasets as datasets\n",
        "# mnist_trainset = datasets.MNIST(root='./data', train=True, download=True, transform=None)\n",
        "\n",
        "# # exmaple from https://github.com/pytorch/examples/blob/master/mnist/main.py\n",
        "# from __future__ import print_function\n",
        "# import argparse\n",
        "# import torch\n",
        "# import torch.nn as nn\n",
        "# import torch.nn.functional as F\n",
        "# import torch.optim as optim\n",
        "# from torchvision import datasets, transforms\n",
        "\n",
        "\n",
        "# class Net(nn.Module):\n",
        "#     def __init__(self, activation):\n",
        "#         super(Net, self).__init__()\n",
        "#         self.conv1 = nn.Conv2d(1, 10, 5, 1)\n",
        "#         self.conv2 = nn.Conv2d(10, 50, 5, 1)\n",
        "#         self.fc1 = nn.Linear(4*4*50, 64)\n",
        "#         self.fc2 = nn.Linear(64, 10)\n",
        "#         if activation == 'relu':\n",
        "#             self.activations = F.relu\n",
        "#         else: # softmax\n",
        "#             self.activations = F.softmax\n",
        "\n",
        "#     def forward(self, x):\n",
        "#         x = self.activations(self.conv1(x))\n",
        "#         x = F.max_pool2d(x, 2, 2)\n",
        "#         x = self.activations(self.conv2(x))\n",
        "#         x = F.max_pool2d(x, 2, 2)\n",
        "#         x = x.view(-1, 4*4*50)\n",
        "#         x = self.activations(self.fc1(x))\n",
        "#         x = self.fc2(x)\n",
        "#         return F.log_softmax(x, dim=1)\n",
        "    \n",
        "# def train(args, model, device, train_loader, optimizer, epoch):\n",
        "#     model.train()\n",
        "#     for batch_idx, (data, target) in enumerate(train_loader):\n",
        "#         data, target = data.to(device), target.to(device)\n",
        "#         optimizer.zero_grad()\n",
        "#         output = model(data)\n",
        "#         loss = F.nll_loss(output, target)\n",
        "#         loss.backward()\n",
        "#         optimizer.step()\n",
        "#         if batch_idx % args.log_interval == 0:\n",
        "#             print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n",
        "#                 epoch, batch_idx * len(data), len(train_loader.dataset),\n",
        "#                 100. * batch_idx / len(train_loader), loss.item()))\n",
        "\n",
        "# def test(args, model, device, test_loader):\n",
        "#     model.eval()\n",
        "#     test_loss = 0\n",
        "#     correct = 0\n",
        "#     with torch.no_grad():\n",
        "#         for data, target in test_loader:\n",
        "\n",
        "#             data, target = data.to(device), target.to(device)\n",
        "#             output = model(data)\n",
        "#             test_loss += F.nll_loss(output, target, reduction='sum').item() # sum up batch loss\n",
        "#             pred = output.argmax(dim=1, keepdim=True) # get the index of the max log-probability\n",
        "#             correct += pred.eq(target.view_as(pred)).sum().item()\n",
        "\n",
        "#     test_loss /= len(test_loader.dataset)\n",
        "\n",
        "#     print('\\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\\n'.format(\n",
        "#         test_loss, correct, len(test_loader.dataset),\n",
        "#         100. * correct / len(test_loader.dataset)))\n",
        "    \n",
        "#     return correct / len(test_loader.dataset)\n",
        "\n",
        "# import skopt\n",
        "# from skopt import gp_minimize, forest_minimize\n",
        "# from skopt.space import Real, Categorical, Integer\n",
        "# from skopt.utils import use_named_args\n",
        "\n",
        "# from skopt.plots import *\n",
        "\n",
        "# dim_num_batch_size_to_base = Integer(low=3, \n",
        "#                                      high=6, \n",
        "#                                      name='log_batch_size')\n",
        "# dim_learning_rate = Real(low=1e-6, high=1e0,\n",
        "#                          prior='log-uniform',\n",
        "#                          name='lr')\n",
        "# dim_activation = Categorical(categories=['relu', 'sigmoid'], \n",
        "#                              name='activation')\n",
        "# dimensions = [dim_num_batch_size_to_base,\n",
        "#               dim_learning_rate,\n",
        "#               dim_activation]\n",
        "\n",
        "# default_parameters = [4, 1e-1, 'relu']\n",
        "\n",
        "# @use_named_args(dimensions=dimensions)\n",
        "# def main(log_batch_size=6, lr=1e-2, activation='relu'):\n",
        "#     # Training settings\n",
        "#     class Args:\n",
        "#         seed = 0\n",
        "#         test_batch_size = 1000\n",
        "#         epochs = 10\n",
        "#         momentum = 0.5\n",
        "#         log_interval = 15000\n",
        "#         def __init__(self,\n",
        "#                      log_batch_size, \n",
        "#                      lr, \n",
        "#                     activation):\n",
        "#             self.activation = activation\n",
        "#             self.batch_size = int(2**log_batch_size)\n",
        "#             self.lr = lr\n",
        "#         def __repr__(self):\n",
        "#             return str(self.__dict__)\n",
        "    \n",
        "#     args = Args(log_batch_size,lr,activation)\n",
        "    \n",
        "#     torch.manual_seed(args.seed)\n",
        "\n",
        "#     kwargs = {}\n",
        "#     train_loader = torch.utils.data.DataLoader(\n",
        "#         datasets.MNIST('./data', train=True, download=True,\n",
        "#                        transform=transforms.Compose([\n",
        "#                            transforms.ToTensor(),\n",
        "#                            transforms.Normalize((0.1307,), (0.3081,))\n",
        "#                        ])),\n",
        "#         batch_size=args.batch_size, shuffle=True, **kwargs)\n",
        "#     test_loader = torch.utils.data.DataLoader(\n",
        "#         datasets.MNIST('./data', train=False, transform=transforms.Compose([\n",
        "#                            transforms.ToTensor(),\n",
        "#                            transforms.Normalize((0.1307,), (0.3081,))\n",
        "#                        ])),\n",
        "#         batch_size=args.test_batch_size, shuffle=True, **kwargs)\n",
        "\n",
        "#     device = 'cuda'\n",
        "#     model = Net(args.activation).to(device)\n",
        "    \n",
        "#     optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum)\n",
        "    \n",
        "#     print ('Args for this run:')\n",
        "#     print (args)\n",
        "#     for epoch in range(1, args.epochs + 1):\n",
        "#         train(args, model, device, train_loader, optimizer, epoch)\n",
        "#         finalacc = test(args, model, device, test_loader)\n",
        "    \n",
        "#     return -finalacc # we will be miniming using scikit-optim\n",
        "\n",
        "# # search_result = gp_minimize(func=main,\n",
        "# #                             dimensions=dimensions,\n",
        "# #                             acq_func='EI', # Expected Improvement.\n",
        "# #                             n_calls=11,\n",
        "# #                             x0=default_parameters)\n",
        "# raise ValueError\n",
        "\n",
        "# import pickle\n",
        "\n",
        "# with open('dump2.pkl', 'rb') as f:\n",
        "#     search_result = pickle.load(f)\n",
        "\n",
        "# fig, ax = plt.subplots(figsize=(16, 6))\n",
        "# ax = plot_convergence(search_result, ax=ax)\n",
        "# format_axes(ax)\n",
        "# plt.savefig(\"MAB_gifs/conv.svg\", ax=ax)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4UwRYATYv0-K",
        "colab_type": "text"
      },
      "source": [
        ""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PVTeqLb7v0-L",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/conv.svg)\n",
        "\n",
        "Looking at the above example, we can see that incorporating Bayesian Optimization isn't a big problem and saves a lot of time we can see that the network was able to get to an accuracy of nearly one in around 3 iterations. That's impressive! Above example has been inspired by Hvass Laboratories' Tutorial on `scikit-optim`. -- footnotes https://github.com/Hvass-Labs/TensorFlow-Tutorials/blob/master/19_Hyper-Parameters.ipynb"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UgJ7q4gJv0-L",
        "colab_type": "text"
      },
      "source": [
        "#### Example 3\n",
        "\n",
        "Using Bayesian Optimization in Random Forests."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "7zeSiVOLv0-L",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "from sklearn.ensemble import RandomForestClassifier"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "UGSucGADv0-N",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def f(Listofpoints, seed=0):\n",
        "    '''Given a grid of hyperparameters we fit the SVM and return\n",
        "    the negative of validation error (we are aiming to minimize error)'''\n",
        "    Listofpoints = np.array(Listofpoints)\n",
        "    Gammas, Cs = Listofpoints[:, 0], Listofpoints[:, 1]\n",
        "    Gammas = np.array(Gammas)\n",
        "    Cs = np.array(Cs)\n",
        "    shpe = Gammas.shape\n",
        "    assert (Gammas.shape == Cs.shape)\n",
        "    accs = []\n",
        "    for gamma, C in zip(Gammas.flatten(), Cs.flatten()):\n",
        "        clf = RandomForestClassifier(n_estimators=gamma, max_depth=C, random_state=seed)\n",
        "        clf.fit(X_train, y_train)\n",
        "        pred_y = clf.predict(X_test)\n",
        "        mask = (pred_y == y_test)\n",
        "        acc = mask.sum()/mask.shape[0]\n",
        "        accs.append(acc)\n",
        "    return np.array(accs).reshape(shpe)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "scrolled": true,
        "id": "JO-3V-Gyv0-P",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "x0 = np.arange(3, 30) # # of trees\n",
        "x1 = np.arange(2, 30) # max_depth\n",
        "\n",
        "x = np.array(list(itertools.product(x0, x1)))\n",
        "print ('x.shape:', x.shape)\n",
        "\n",
        "X0, X1 = np.meshgrid(x0, x1)\n",
        "xx = np.vstack([X0.reshape(X0.size), X1.reshape(X1.size)]).T\n",
        "\n",
        "GT = f(x)\n",
        "GT = GT.reshape(X0.shape)\n",
        "print('GT.shape:', GT.shape)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Fq8t7sE8v0-T",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "nnsvm(plt.rcParams, 4)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Z6AM-Kwav0-V",
        "colab_type": "text"
      },
      "source": [
        "Let us now show the some plots showing Bayesian Optimization for learning nice hyperparameters for our Random Forest model."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "scrolled": false,
        "id": "myHaPFrtv0-V",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "eps = 0.05\n",
        "\n",
        "acq_params = {'eps':eps}\n",
        "dirName, mtn_pi = plot_acquisition3d(\n",
        "    PI, mean_fn = 0.60, \n",
        "    acq_params=acq_params,\n",
        "    z0lim=uplow,\n",
        "    it=10\n",
        ")\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/RFpi3d-0.05-mat.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ORbWLKryv0-X",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/RFpi3d-0.05-mat.gif)\n",
        "\n",
        "Above we see a gif showing the work of teh _Probability of Improvement_ acquisition function in finding the best hyperparameters."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "bZ5Xr3x7v0-X",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "eps = 0.5\n",
        "acq_params = {'eps':eps}\n",
        "dirName, mtn_ei = plot_acquisition3d(\n",
        "    EI, mean_fn = 0.60, \n",
        "    acq_params=acq_params,\n",
        "    z0lim=uplow,\n",
        "    it=10\n",
        ")\n",
        "\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/RFei3d-0.0001-mat.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jmIXu0jEv0-Y",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/RFei3d-0.0001-mat.gif)\n",
        "\n",
        "Above we see a gif showing the work of teh _Expected Improvement_ acquisition function in finding the best hyperparameters."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "scrolled": true,
        "id": "4XwAwGcTv0-Z",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "v = 1\n",
        "delta = 2\n",
        "acq_params = {\n",
        "    'v':v,\n",
        "    'delta':delta\n",
        "}\n",
        "dirName, mtn_gp_ucb = plot_acquisition3d(\n",
        "    GP_UCB, mean_fn = 0.60, \n",
        "    acq_params=acq_params,\n",
        "    z0lim=uplow,\n",
        "    it=10\n",
        ")\n",
        "\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/RFgp3d-1-2-mat.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Yk1Ktle1v0-a",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/RFgp3d-1-2-mat.gif)\n",
        "\n",
        "Above we see a gif showing the work of the _Guassian Processes Upper Confidence Bound_ acquisition function in finding the best hyperparameters. This by far seems to perform the best with getting quite close to the global optimum value of hyperparameters (found using brute force)."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "scrolled": false,
        "id": "wP1K9VUov0-a",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "stats = RunningStat(len(mtn_gp_ucb))\n",
        "for seed in range(10):\n",
        "    dirName, temp = plot_acquisition3d(\n",
        "        Rand, mean_fn = 0.60, \n",
        "        acq_params=acq_params,\n",
        "        z0lim=uplow,\n",
        "        it=10,\n",
        "        seed=seed\n",
        "    )\n",
        "    stats.push(temp)\n",
        "\n",
        "mtn_rand = stats.mean\n",
        "rand_sig = stats.std\n",
        "\n",
        "\n",
        "# gify!\n",
        "# !convert -delay {delay} -loop 0 {dirName}/*.png {gifDir}/RFrand3d.gif"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "v7Jf88gyv0-c",
        "colab_type": "text"
      },
      "source": [
        "![](MAB_gifs/RFrand3d.gif)\n",
        "\n",
        "Now our favourite random acquisition function. :)"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "aQobziQzv0-c",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "nnsvm(plt.rcParams, 4)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "7iF5tln7v0-d",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "max_f = np.max(GT.flatten())\n",
        "globalopt = [max_f] * len(mtn_rand)\n",
        "\n",
        "mtns = [mtn_rand, globalopt, mtn_pi, mtn_ei, mtn_gp_ucb]\n",
        "names = ['Random', 'GlobalOpt', 'Probability of Improvement', 'Expected Improvement', 'GP-UCB']\n",
        "markers = ['*-', 'o:', 'v--', '>-.', '<:']\n",
        "\n",
        "xx = range(len(mtns[0]))\n",
        "plt.figure(figsize=(14, 8))\n",
        "for n, m, mm in zip(names, mtns, markers):\n",
        "    plt.plot(xx, m, mm, label=n, alpha=0.7, lw=2.7)\n",
        "\n",
        "plt.legend()\n",
        "plt.xlabel('# of Evaluations')\n",
        "plt.ylabel('Max Accuracy Reached')\n",
        "plt.title('Comparison of different Acquisition Functions')\n",
        "format_axes(plt.gca())\n",
        "plt.savefig(\"MAB_gifs/RFcomp3d.svg\", bbox_inches=\"tight\")\n",
        "plt.show()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BlsaJlkov0-f",
        "colab_type": "text"
      },
      "source": [
        "We see GP-UCB performed the best in this case. Random that was performing really nice in the last single dimensional example seems to perform much badly in this case. This can be attributed to the increase in the number of dimensions, it's much difficult to get to the optimal value by using random search."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NaqpF2lZv0-f",
        "colab_type": "text"
      },
      "source": [
        "## Conclusions\n",
        "We see that for the case of multi-arm bandit we have a bit different problem as compared to the active learning problem and therefore we have the different objective functions that we try to maximize for the query points.\n",
        "\n",
        "## Embrace Bayesian Optimization\n",
        "After reading through the blog post, you might have been sold on the idea about the time you can save by asking Bayesian Optimizer to find the best hyperparameters for your amazing model. There are a plethora of Bayesian Optimization libraries available. I have linked a few known ones. Do check them out.\n",
        "\n",
        "- [scikit-optimize](https://scikit-optimize.github.io/) -- footnotes really nice tutorial showcasing hyperparameter optimization on a neural network. [link](https://github.com/Hvass-Labs/TensorFlow-Tutorials/blob/master/19_Hyper-Parameters.ipynb)\n",
        "- [sigopt](https://app.sigopt.com/docs/overview/python)\n",
        "- [hyperopt](http://hyperopt.github.io/hyperopt/)\n",
        "- [spearmint](https://github.com/HIPS/Spearmint)\n",
        "- [MOE](https://github.com/Yelp/MOE)\n",
        "\n",
        "### Caution\n",
        "We need to take care while using Bayesian Optimization. Bayesian Optimization based on Gaussian Processes Regression is highly sensitive to the kernel used. For example, if you are using [Matern](https://scikit-learn.org/stable/modules/generated/sklearn.gaussian_process.kernels.Matern.html) kernel, we are implicitly assuming that the function we are trying to optimize is first order differentiable.\n",
        "\n",
        "A nice list of tips and tricks one should have a look at if you aim to use Bayesian Optimization in your workflow is from this amazing post by Thomas on [Bayesian Optimization with sklearn](https://thuijskens.github.io/2016/12/29/bayesian-optimisation/).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "L3sia7KUv0-g",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        ""
      ],
      "execution_count": 0,
      "outputs": []
    }
  ]
}